package edu.bu.cs511.p5;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyAdapter;

/*
 * This is the demo given in class and should probably not even be in
 * the project directory, thus why it is undocumented.
 */
public class SchedulerDemo extends javax.swing.JFrame
{
    // constants used to define the game field and bounds
    public static final int DEMO_WINDOW_HEIGHT = 256;
    public static final int DEMO_WINDOW_WIDTH = 640;
    
    private static final long serialVersionUID = 1L;
    
    // member variables
    private KeyHandler kh;      // handle keyboard events

    private static final int EXECUTION_WINDOW = 200;
    private static final int MAX_UNIQUE_IDS = 20;
    private static int tapePtr = 0;
    private static int[] executionTrace;
    private static int[] uniqueIds;
    private static int uniqueIdsLength;
    private static Color[] colorUniques;
    private int phaseNo = 0;
    private String phaseInfo = "";
    
    public SchedulerDemo()
    {
        initComponents();
	control = new Object();

        // subscribe for events from keyboard and mouse
        kh = new KeyHandler();
        addKeyListener(kh);
                
	executionTrace = new int[EXECUTION_WINDOW];
	colorUniques = new Color[MAX_UNIQUE_IDS];
	uniqueIds = new int[MAX_UNIQUE_IDS];
	uniqueIdsLength = 0;
    } // public SchedulerDemo()

    public static int inUnique(int id)
    {
	for (int i = 0; i < uniqueIdsLength ; i++) {
	    if (uniqueIds[i] == id) return i;
	}
	return -1;
    }

    /*
     * Insertion sort.
     */ 
    public static void insertIntoUnique(int id, Color c)
    {
	for (int i = 0; i < uniqueIdsLength ; i++) {
	    if (id < uniqueIds[i]) {
		for (int j = uniqueIdsLength ; j >= i ; j--) {
		    uniqueIds[j] = uniqueIds[j-1];
		    colorUniques[j] = colorUniques[j-1];
		}

		uniqueIds[i] = id;
		colorUniques[i] = c;
		uniqueIdsLength++;

		return;
	    }
	}
	uniqueIds[uniqueIdsLength] = id;
	colorUniques[uniqueIdsLength] = c;
	uniqueIdsLength++;
	
	return;
    }

    public static synchronized void notifyOpcodeExecution(int opcodeId, Color opcodeColor)
    {
	int index = inUnique(opcodeId);
	if (index == -1) {
	    insertIntoUnique(opcodeId, opcodeColor);
	} else {
	    /* just make sure the colors are consistent */
	    colorUniques[index] = opcodeColor;
	}

	executionTrace[tapePtr] = opcodeId;
	tapePtr = (tapePtr + 1) % EXECUTION_WINDOW;
	return;
    }

    private int getCount(int id)
    {
	int count = 0;
	
	for (int i = 0 ; i < EXECUTION_WINDOW ; i++) {
	    if (executionTrace[i] == id) count++;
	}
	
	return count;
    }

    /**
     * The paint method handles the graphical rendering. 
     * 
     * From any other method, call repaint() to get this method 
     * to redraw the screen.
     */
    public void paint(Graphics g)
    {   
	int topY = 100;
	int currentX = 0;

	g.setColor(Color.WHITE);
	g.fillRect(0, 0, (DEMO_WINDOW_WIDTH-1)*2, DEMO_WINDOW_HEIGHT);
	
	for (int i = 0 ; i < uniqueIdsLength ; i++) {
	//for (int i = uniqueIdsLength-1 ; i >=0 ; i--) {
	    double percent = ((double)getCount(uniqueIds[i])/(double)EXECUTION_WINDOW);
	    int width = (int)((double)(DEMO_WINDOW_WIDTH-1) * percent);

	    if (width+currentX >= DEMO_WINDOW_WIDTH) {
		width = DEMO_WINDOW_WIDTH-currentX-1;
	    }

	    g.setColor(colorUniques[i]);
	    g.fillRect(currentX, topY, currentX+width, DEMO_WINDOW_HEIGHT-1);

	    g.setColor(Color.BLACK);
	    g.drawString(uniqueIds[i] +": "+ (int)(percent*100), currentX+3, topY+30);
	    g.setColor(Color.WHITE);
	    g.drawString(uniqueIds[i] +": "+ (int)(percent*100), currentX+3, DEMO_WINDOW_HEIGHT);

	    currentX += width;
	}
	g.setColor(Color.BLACK);
	g.drawString("Phase "+phaseNo+": "+phaseInfo, 10, 50);
	g.setColor(Color.WHITE);
	g.fillRect(DEMO_WINDOW_WIDTH-1, topY, (DEMO_WINDOW_WIDTH-1)*2, DEMO_WINDOW_HEIGHT-1);
    } // public void paint(Graphics g)

    public void initComponents()
    {
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setSize(DEMO_WINDOW_WIDTH, DEMO_WINDOW_HEIGHT);
        setTitle("SchedulerDemo");

    } // public void initComponents()
               
    /** The main method starts the game and delegates all work 
     *  to the play method.
     * 
     * @param args
     */
    public static void main(String[] args)
    {
        SchedulerDemo game = new SchedulerDemo();
        game.setVisible(true);
        game.play();                
              
    } // public static void main(String[] args)

    private final Object control;
    private ThreadManager thdManager;
    
    /** The play method is where the main game loop resides. 
     */
    public void play()
    {
	SchedTask schedTask1, schedTask2, schedTask3, schedTask4;
	SchedTask root;
	OpcodeTask one, two, three, four, five, six, seven, eight, nine;

        /* make the schedulers */
        IScheduler schedRoot, schedRoot_2, sched1, sched1_2, sched2, sched2_2, sched3;
        schedRoot = new ProportionalScheduler(1);
        schedRoot_2 = new ProportionalScheduler(2);
        sched1 = new ProportionalScheduler(1);
        sched1_2 = new ProportionalScheduler(2);
        sched2 = new FPScheduler(1);
        sched2_2 = new FPScheduler(2);
	sched3 = new RRScheduler(1);
	
	DemoOpcode opcode1, opcode2, opcode3, opcode4, opcode5, opcode6, opcode7, opcode8, opcode9;

        opcode1 = new DemoOpcode(7, 1, new Color(85, 0, 0));
        opcode2 = new DemoOpcode(8, 1, new Color(170, 0, 0));
        opcode3 = new DemoOpcode(9, 1, new Color(255, 0, 0));
        opcode4 = new DemoOpcode(4, 1, new Color(0, 85, 0));
        opcode5 = new DemoOpcode(5, 1, new Color(0, 170, 0));
	opcode6 = new DemoOpcode(6, 1, new Color(0, 255, 0));
        opcode7 = new DemoOpcode(1, 1, new Color(0, 0, 85));
        opcode8 = new DemoOpcode(2, 1, new Color(0, 0, 170));
        opcode9 = new DemoOpcode(3, 1, new Color(0, 0, 255));


        /*
	 * The below should create a hierarchy like so:
	 *
	 * opcode1[1] opcode2[0] opcode3[0] opcode4 opcode5 opcode6[0.5] opcode7[0.1] opcode8[0.1]
	 *      |         |       |          |     |                  |        |        |
	 *      FPScheduler(2)[0.5]     RRScheduler(1)[0.2]     ProportionalScheduler(1)[0.3]
	 *                  |                   |                |
	 *                  +-------ProportionalScheduler(2)-----+
	 *
	 * The values in () are how many threads (max) that scheduler
	 * will allow.  The values in [] are the parameters set for
	 * that Task within the parent scheduler
	 *
	 */

        thdManager = SchedHierarchy.createThdManager();

        /* create root */
        root = SchedHierarchy.rootScheduler(thdManager, schedRoot);

	new RePainter().start();

        new ThdManagerThd().start();

	waitForKeypress("round robin tasks receive exactly the same CPU share.");

	schedTask3 = SchedHierarchy.addScheduler(root, sched3, "0.25");
	if ((seven = SchedHierarchy.addOpcode(schedTask3, opcode7)) == null)
            System.out.println("seven is null.\n");
	seven.setInSXE(false);
	seven.enableOpcode();

	if ((eight = SchedHierarchy.addOpcode(schedTask3, opcode8)) == null)
            System.out.println("eight is null.\n");
	eight.setInSXE(false);
	eight.enableOpcode();

	if ((nine = SchedHierarchy.addOpcode(schedTask3, opcode9)) == null)
            System.out.println("nine is null.\n");
	nine.setInSXE(false);
	nine.enableOpcode();



	waitForKeypress("in fixed priority scheduling, tasks with equal priority receive equal CPU shares.");

	schedTask2 = SchedHierarchy.addScheduler(root, sched2, "0.25");
	if ((four = SchedHierarchy.addOpcode(schedTask2, opcode4, "0")) == null)
            System.out.println("four is null.\n");
	four.setInSXE(false);
	four.enableOpcode();

	if ((five = SchedHierarchy.addOpcode(schedTask2, opcode5, "0")) == null)
            System.out.println("five is null.\n");
	five.setInSXE(false);
	five.enableOpcode();

	waitForKeypress("in fixed priority scheduling, higher priority tasks get 100% of the CPU.");

	if ((six = SchedHierarchy.addOpcode(schedTask2, opcode6, "1")) == null)
            System.out.println("six is null.\n");
	six.setInSXE(false);
	six.enableOpcode();



	waitForKeypress("In proportional share scheduling, you can define portions of the CPU for tasks.  This takes 15%.");

	schedTask1 = SchedHierarchy.addScheduler(root, sched1, "0.5");
	if ((one = SchedHierarchy.addOpcode(schedTask1, opcode1, "0.15")) == null)
            System.out.println("one is null.\n");
	one.setInSXE(false);
	one.enableOpcode();

	waitForKeypress("adding task to receive 35% of the CPU (or more than twice as much as the first task)");

        if ((two = SchedHierarchy.addOpcode(schedTask1, opcode2, "0.35")) == null)
            System.out.println("two is null.\n");
	two.setInSXE(false);
	two.enableOpcode();

	waitForKeypress("adding task to receive 50% of the CPU");

        if ((three = SchedHierarchy.addOpcode(schedTask1, opcode3, "0.50")) == null)
            System.out.println("three is null.\n");
	three.setInSXE(false);
	three.enableOpcode();
	
	waitForKeypress("multiple threads in a system weaken guarantees schedulers can make.");

	one.endTask();
	two.endTask();
	three.endTask();
	schedTask1.endTask();
	four.endTask();
	five.endTask();
	six.endTask();
	schedTask2.endTask();
	seven.endTask();
	eight.endTask();
	nine.endTask();
	schedTask3.endTask();
	root.endTask();

        root = SchedHierarchy.rootScheduler(thdManager, schedRoot_2);

	schedTask2 = SchedHierarchy.addScheduler(root, sched2_2, "0.25");
	if ((four = SchedHierarchy.addOpcode(schedTask2, opcode4, "0")) == null)
            System.out.println("four is null.\n");
	four.setInSXE(false);
	four.enableOpcode();

	if ((five = SchedHierarchy.addOpcode(schedTask2, opcode5, "0")) == null)
            System.out.println("five is null.\n");
	five.setInSXE(false);
	five.enableOpcode();

	waitForKeypress("adding a higher priority task.");

	if ((six = SchedHierarchy.addOpcode(schedTask2, opcode6, "1")) == null)
            System.out.println("six is null.\n");
	six.setInSXE(false);
	six.enableOpcode();

	
    } //  public void play()

    private void waitForKeypress(String msg)
    {
	try {
	    synchronized(control) {
		control.wait();
	    }
	} catch (InterruptedException ie) {
	    System.out.println(ie.getMessage());
	}

	phaseNo++;
	phaseInfo = msg;
    }

    private void sendKeypress()
    {
	synchronized(control) {
	    control.notify();
	}
    }

    public class RePainter extends Thread
    {
	public RePainter() {}

	public void run() 
	{
	    while (true) {
		repaint();
		try {
		    sleep(200);
		} catch (InterruptedException ie) {}
	    }
	}
    }

    public class ThdManagerThd extends Thread
    {
	public ThdManagerThd() {}

	public void run() 
	{
	    thdManager.executeTask();
	}
    }

    /** key handler class -- enables listening for key presses
     */
    public class KeyHandler extends KeyAdapter
    {
        public void keyPressed(KeyEvent ke)
        {            
            int key = ke.getKeyCode();
	    sendKeypress();

            // PUT YOUR KEYBOARD EVENT HANDLER CODE HERE
            
        } // public void keyPressed(KeyEvent ke)
    } // public class KeyHandler extends KeyAdapter
    
} // end class SchedulerDemo

